#include <ncurses.h>
#include <time.h>
#include <stdlib.h>

#define SCRW 80
#define SCRH 24


struct SnakeGame{
   int score;
   int fy;
   int fx;
   int dead;
        
   int dir;
   int speed;   
   
   char snake[SCRW*SCRH/2][3];
   int snake_l;

   bool classic;
   bool twoplayers;
};

void game_redraw(SnakeGame* controle);
void game_redraw(SnakeGame* controle, SnakeGame* controle_player2);
void snake_move(SnakeGame* controle, SnakeGame* controle_player2, int z);
void snake_move(SnakeGame* controle, int z);
void random_food(SnakeGame* controle);
void random_food(SnakeGame* controle, SnakeGame* controle_player2);
void snake_run(SnakeGame* controle);
void snake_run(SnakeGame* controle, SnakeGame* controle_player2);
void snake_append(SnakeGame* controle);
void snake_append(SnakeGame* controle, SnakeGame* controle_player2);
void delay_speed(SnakeGame* controle);
void game_think(SnakeGame* controle);
void game_think(SnakeGame* controle, SnakeGame* controle_player2);
void game(SnakeGame* controle);
void game(SnakeGame* controle, SnakeGame* controle_player2);
void game_menu(SnakeGame* controle);


int main(int argc, char* argv[]){
   srand(time(NULL));

   SnakeGame controle;

   controle.score = 0;
   controle.fx = rand()%79;
   controle.fy = rand()%23;
   controle.dir = 1;
   controle.dead = 0;
   controle.snake_l = 3;
   controle.speed = 4;
   controle.snake[0][0] = 12;
   controle.snake[0][1] = 40;
   controle.snake[1][0] = 13;
   controle.snake[1][1] = 40;
   controle.snake[2][0] = 14;
   controle.snake[2][1] = 40;

   initscr();
   raw();  
   keypad(stdscr, TRUE);
   noecho();
   curs_set(0);
   
   game_menu(&controle);
   
   endwin();
}

void game_redraw(SnakeGame* controle){
   refresh();
   clear();

   if(controle->dead != 0){
      controle->snake_l = 3;
      for(int z = 0; z < controle->snake_l; z++){
      controle->snake[z][0] = 12 + z;
      controle->snake[z][1] = 40;             
   }

   controle->dir = 1;
   random_food(controle);

   }   

   mvaddch(controle->fy, controle->fx, ACS_BULLET);

   for(int l = 0; l < controle->snake_l; l++){
      mvaddch(controle->snake[l][0], controle->snake[l][1], ACS_DIAMOND);
   }
   
   if(controle->classic){
      for(int x = 0; x <= 80; x++){
         mvprintw(0, x, "#");
         mvprintw(23, x, "#");
      }
      for(int y = 0; y <= 24; y++){
         mvprintw(y, 0, "#");
         mvprintw(y, 79, "#");
      }    
   }
}

void game_redraw(SnakeGame* controle, SnakeGame* controle_player2){
   refresh();
   clear();

   if(controle->dead != 0){
      controle->snake_l = 3;
      for(int z = 0; z < controle->snake_l; z++){
         controle->snake[z][0] = 12 + z;
         controle->snake[z][1] = 40;             
       }

     controle->dir = 1;
     random_food(controle);
   }

   if(controle_player2->dead != 0){
      controle_player2->snake_l = 3;
      for(int z = 0; z < controle_player2->snake_l; z++){
        controle_player2->snake[z][0] = 12 + z;
        controle_player2->snake[z][1] = 35;             
      }

     controle_player2->dir = 1;
     random_food(controle_player2);

   }   

   mvaddch(controle->fy, controle->fx, 'X');
   mvaddch(controle_player2->fy, controle_player2->fx, '@');

   for(int l = 0; l < controle->snake_l; l++){
      mvaddch(controle->snake[l][0], controle->snake[l][1], ACS_DIAMOND);
   }

   for(int l = 0; l < controle_player2->snake_l; l++){
      mvaddch(controle_player2->snake[l][0], controle_player2->snake[l][1], ACS_DIAMOND);
   }
   
   if(controle->classic){
      for(int x = 0; x <= 80; x++){
         mvprintw(0, x, "#");
         mvprintw(23, x, "#");
      }
      for(int y = 0; y <= 24; y++){
         mvprintw(y, 0, "#");
         mvprintw(y, 79, "#");
      }    
   }
}

void snake_move(SnakeGame* controle, int z){
   
switch(z){

   case KEY_UP:
      if(controle->dir == 2) break;         
         controle->dir = 1;
   break;

   case KEY_DOWN:
      if(controle->dir == 1) break;
         controle->dir = 2;
   break;
        
   case KEY_LEFT:
      if(controle->dir == 4) break;
       controle->dir = 3;
   break;

   case KEY_RIGHT:
      if(controle->dir == 3) break;
       controle->dir = 4;
   break;
        
   case 'f':
      controle->speed = 1;
      delay_speed(controle);
   break;

   case 'm':
      controle->speed = 3;
      delay_speed(controle);
   break;

   case 'l':
      controle->speed = 5;
      delay_speed(controle);
   break;

        }
        
}

void snake_move(SnakeGame* controle, SnakeGame* controle_player2, int z){
   
switch(z){

   case KEY_UP:
      if(controle->dir == 2) break;         
         controle->dir = 1;
   break;

   case KEY_DOWN:
      if(controle->dir == 1) break;
         controle->dir = 2;
   break;
        
   case KEY_LEFT:
      if(controle->dir == 4) break;
       controle->dir = 3;
   break;

   case KEY_RIGHT:
      if(controle->dir == 3) break;
       controle->dir = 4;
   break;
   
   case 'w':
      if(controle_player2->dir == 2) break;
      controle_player2->dir = 1;
   break;

   case 's':
      if(controle_player2->dir == 1) break;
      controle_player2->dir = 2;
   break;

   case 'a':
      if(controle_player2->dir == 4) break;
      controle_player2->dir = 3;
   break;

   case 'd':
      if(controle_player2->dir == 3) break;
      controle_player2->dir = 4;
   break;
        
   case 'f':
      controle->speed = 1;
      delay_speed(controle);
   break;

   case 'm':
      controle->speed = 3;
      delay_speed(controle);
   break;

   case 'l':
      controle->speed = 5;
      delay_speed(controle);
   break;

        }
        
}

void snake_run(SnakeGame* controle){

   for(int x = controle->snake_l - 1; x > 0; x--){      
      controle->snake[x][0] = controle->snake[x-1][0];
      controle->snake[x][1] = controle->snake[x-1][1];
        }   

   switch(controle->dir){
    case 2:
     if(controle->snake[0][0] == 23 && !controle->classic) { controle->snake[0][0] = 0; break;}
     controle->snake[0][0]++;
    break;
    
    case 1:
     if(controle->snake[0][0] == 0 && !controle->classic) { controle->snake[0][0] = 23; break;}
     controle->snake[0][0]--;
    break;

    case 3:
     if(controle->snake[0][1] == 0 && !controle->classic) { controle->snake[0][1] = 79; break;}
     controle->snake[0][1]--;
    break;

    case 4:
     if(controle->snake[0][1] == 79 && !controle->classic) { controle->snake[0][1] = 0; break;}
     controle->snake[0][1]++;
    break;
   }

}

void snake_run(SnakeGame* controle, SnakeGame* controle_player2){

   for(int x = controle->snake_l - 1; x > 0; x--){      
      controle->snake[x][0] = controle->snake[x-1][0];
      controle->snake[x][1] = controle->snake[x-1][1];
        }   

   for(int x = controle_player2->snake_l - 1; x > 0; x--){
      controle_player2->snake[x][0] = controle_player2->snake[x-1][0];
      controle_player2->snake[x][1] = controle_player2->snake[x-1][1];
      }

   switch(controle->dir){
    case 2:
     if(controle->snake[0][0] == 23 && !controle->classic) { controle->snake[0][0] = 0; break;}
     controle->snake[0][0]++;
    break;
    
    case 1:
     if(controle->snake[0][0] == 0 && !controle->classic) { controle->snake[0][0] = 23; break;}
     controle->snake[0][0]--;
    break;

    case 3:
     if(controle->snake[0][1] == 0 && !controle->classic) { controle->snake[0][1] = 79; break;}
     controle->snake[0][1]--;
    break;

    case 4:
     if(controle->snake[0][1] == 79 && !controle->classic) { controle->snake[0][1] = 0; break;}
     controle->snake[0][1]++;
    break;
   }

   switch(controle_player2->dir){
    case 2:
     if(controle_player2->snake[0][0] == 23 && !controle_player2->classic) { controle_player2->snake[0][0] = 0; break;}
     controle_player2->snake[0][0]++;
    break;
    
    case 1:
     if(controle_player2->snake[0][0] == 0 && !controle_player2->classic) { controle_player2->snake[0][0] = 23; break;}
     controle_player2->snake[0][0]--;
    break;

    case 3:
     if(controle_player2->snake[0][1] == 0 && !controle_player2->classic) { controle_player2->snake[0][1] = 79; break;}
     controle_player2->snake[0][1]--;
    break;

    case 4:
     if(controle_player2->snake[0][1] == 79 && !controle_player2->classic) { controle_player2->snake[0][1] = 0; break;}
     controle_player2->snake[0][1]++;
    break;
   }

}

void game_think(SnakeGame* controle) {
   if(controle->snake[0][0] == controle->fy && controle->fx == controle->snake[0][1]){
      controle->snake_l++;
      snake_append(controle);
      random_food(controle);
   }   
   
   if(controle->classic){   
      if(controle->snake[0][0] <= 0){ controle->snake[0][0] = 0; controle->dead = 1;}
      if(controle->snake[0][0] >= 23){ controle->snake[0][0] = 24; controle->dead = 1;}
      if(controle->snake[0][1] <= 0){ controle->snake[0][1] = 0; controle->dead = 1;}
      if(controle->snake[0][1] >= 79){ controle->snake[0][1] = 80; controle->dead = 1;}
   }
   
   for(int x = 1; x < controle->snake_l; x++){
      if(controle->snake[0][0] == controle->snake[x][0] && controle->snake[0][1] == controle->snake[x][1]) { 
         controle->dead = 1;
         }
   }
}

void game_think(SnakeGame* controle, SnakeGame* controle_player2) {
   if(controle->snake[0][0] == controle->fy && controle->fx == controle->snake[0][1]){
      controle->snake_l++;
      snake_append(controle);
      random_food(controle);
   }   

   if(controle_player2->snake[0][0] == controle_player2->fy && controle_player2->fx == controle_player2->snake[0][1]){
      controle_player2->snake_l++;
      snake_append(controle_player2);
      random_food(controle_player2);
   }   
   
   if(controle->classic){   
      if(controle->snake[0][0] <= 0){ controle->snake[0][0] = 0; controle->dead = 1;}
      if(controle->snake[0][0] >= 23){ controle->snake[0][0] = 24; controle->dead = 1;}
      if(controle->snake[0][1] <= 0){ controle->snake[0][1] = 0; controle->dead = 1;}
      if(controle->snake[0][1] >= 79){ controle->snake[0][1] = 80; controle->dead = 1;}
   }

   if(controle_player2->classic){   
      if(controle_player2->snake[0][0] <= 0){ controle_player2->snake[0][0] = 0; controle_player2->dead = 1;}
      if(controle_player2->snake[0][0] >= 23){ controle_player2->snake[0][0] = 24; controle_player2->dead = 1;}
      if(controle_player2->snake[0][1] <= 0){ controle_player2->snake[0][1] = 0; controle_player2->dead = 1;}
      if(controle_player2->snake[0][1] >= 79){ controle_player2->snake[0][1] = 80; controle_player2->dead = 1;}
   }
   
   for(int x = 1; x < controle->snake_l; x++){
      if(controle->snake[0][0] == controle->snake[x][0] && controle->snake[0][1] == controle->snake[x][1]) { 
         controle->dead = 1;
         }
      for(int x = 1; x < controle_player2->snake_l; x++){
         if(controle->snake[0][0] == controle_player2->snake[x][0] && controle->snake[0][1] == controle_player2->snake[x][1]){
            controle->dead = 1;
            controle_player2->dead = 1;
         }
      }
   }

   for(int x = 1; x < controle_player2->snake_l; x++){
      if(controle_player2->snake[0][0] == controle_player2->snake[x][0] && controle_player2->snake[0][1] == controle_player2->snake[x][1]){
         controle_player2->dead = 1;
      }
   }
}

void snake_append(SnakeGame* controle) {
   if(controle->snake[controle->snake_l-3][0] - controle->snake[controle->snake_l-2][0] == 1) {
      controle->snake[controle->snake_l - 1][0] = controle->snake[controle->snake_l-2][0] - 1;
      }
   if(controle->snake[controle->snake_l-3][0] - controle->snake[controle->snake_l-2][0] == -1) {
      controle->snake[controle->snake_l - 1][0] = controle->snake[controle->snake_l-2][0] + 1;
      }
   if(controle->snake[controle->snake_l-3][1] - controle->snake[controle->snake_l-2][1] == -1) {
      controle->snake[controle->snake_l - 1][1] = controle->snake[controle->snake_l-2][1] + 1;
      }
   if(controle->snake[controle->snake_l-3][1] - controle->snake[controle->snake_l-2][1] == 1) {
      controle->snake[controle->snake_l - 1][1] = controle->snake[controle->snake_l-2][1] - 1;
      }

}

void snake_append(SnakeGame* controle, SnakeGame* controle_player2) {
   if(controle->snake[controle->snake_l-3][0] - controle->snake[controle->snake_l-2][0] == 1) {
      controle->snake[controle->snake_l - 1][0] = controle->snake[controle->snake_l-2][0] - 1;
      }
   if(controle->snake[controle->snake_l-3][0] - controle->snake[controle->snake_l-2][0] == -1) {
      controle->snake[controle->snake_l - 1][0] = controle->snake[controle->snake_l-2][0] + 1;
      }
   if(controle->snake[controle->snake_l-3][1] - controle->snake[controle->snake_l-2][1] == -1) {
      controle->snake[controle->snake_l - 1][1] = controle->snake[controle->snake_l-2][1] + 1;
      }
   if(controle->snake[controle->snake_l-3][1] - controle->snake[controle->snake_l-2][1] == 1) {
      controle->snake[controle->snake_l - 1][1] = controle->snake[controle->snake_l-2][1] - 1;
      }

   if(controle_player2->snake[controle_player2->snake_l-3][0] - controle_player2->snake[controle_player2->snake_l-2][0] == 1) {
      controle_player2->snake[controle_player2->snake_l - 1][0] = controle_player2->snake[controle_player2->snake_l-2][0] - 1;
      }
   if(controle_player2->snake[controle_player2->snake_l-3][0] - controle_player2->snake[controle_player2->snake_l-2][0] == -1) {
      controle_player2->snake[controle_player2->snake_l - 1][0] = controle_player2->snake[controle_player2->snake_l-2][0] + 1;
      }
   if(controle_player2->snake[controle_player2->snake_l-3][1] - controle_player2->snake[controle_player2->snake_l-2][1] == -1) {
      controle_player2->snake[controle_player2->snake_l - 1][1] = controle_player2->snake[controle_player2->snake_l-2][1] + 1;
      }
   if(controle_player2->snake[controle_player2->snake_l-3][1] - controle_player2->snake[controle_player2->snake_l-2][1] == 1) {
      controle_player2->snake[controle_player2->snake_l - 1][1] = controle_player2->snake[controle_player2->snake_l-2][1] - 1;
      }
}

void random_food(SnakeGame* controle){
   controle->fy = rand() % 21 + 1;  
   controle->fx = rand() % 77 + 1;
   controle->dead = 0;
   game_redraw(controle);
}

void random_food(SnakeGame* controle, SnakeGame* controle_player2){
   controle->fy = rand() % 21 + 1;  
   controle->fx = rand() % 77 + 1;
   controle->dead = 0;
   
   controle_player2->fy = rand() % 21 + 1;
   controle_player2->fx = rand() % 77 + 1;
   controle_player2->dead = 0; 
   game_redraw(controle, controle_player2);
}

void delay_speed(SnakeGame* controle){
   halfdelay(controle->speed);
}

void game(SnakeGame* controle){
   int z;
   delay_speed(controle);   
   
   while((z = getch()) != 27){
      snake_move(controle, z);
      snake_run(controle);
      game_think(controle);
      game_redraw(controle);
   }
   game_menu(controle);

}

void game(SnakeGame* controle, SnakeGame* controle_player2){
   int z;
   delay_speed(controle);   
   
   while((z = getch()) != 27){
      snake_move(controle, controle_player2, z);
      snake_run(controle, controle_player2);
      game_think(controle, controle_player2);
      game_redraw(controle, controle_player2);
   }
   game_menu(controle);

}

void game_menu(SnakeGame* controle){
   refresh();
   clear();
   
   mvprintw(10, 30, "1 - Classic Snake Game");
   mvprintw(11, 30, "2 - Snake Game");
   mvprintw(12, 30, "ESC - Exit");
   mvprintw(13, 30, "F9 - 2 Players Mode");
   
   halfdelay(150);
   int z = getch();

   if(z == '1'){ controle->classic = true; game(controle);}
   else if(z == '2'){ controle->classic = false; game(controle);} 
   else if(z == 27){ endwin();}  
   else if(z == KEY_F(9)){
      SnakeGame controle_player2;

      controle_player2.score = 0;
      controle_player2.fx = rand()%79;
      controle_player2.fy = rand()%23;
      controle_player2.dir = 2;
      controle_player2.dead = 0;
      controle_player2.snake_l = 3;
      controle_player2.speed = 4;
      controle_player2.snake[0][0] = 14;
      controle_player2.snake[0][1] = 35;
      controle_player2.snake[1][0] = 13;
      controle_player2.snake[1][1] = 35;
      controle_player2.snake[2][0] = 12;
      controle_player2.snake[2][1] = 35;
      controle_player2.twoplayers = true;

      controle_player2.classic = false;
      controle->classic = false;

      controle->twoplayers = true;
      
      game(controle, &controle_player2);
   }
   else{refresh(); clear(); mvprintw(12, 30, "Opção não existe."); endwin();}
   
}

